package com.jcompressor;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import com.jcompressor.entities.Project;
import com.jcompressor.exceptions.JcompressorException;
import com.jcompressor.preferences.ConfigKeys;
import com.jcompressor.preferences.ConfigurationHandler;
import com.jcompressor.utils.JcompressorUtils;
import com.yahoo.platform.yui.compressor.YUICompressor;

public class Jcompressor {
	
	private DataStream stream;
	private ConfigurationHandler handler;
	private Map<String, String> properties;
	private List<String> configurations;
	private final List<Project> projects;
	
	public Jcompressor() {
		this.stream = new DataStream();
		this.handler = new ConfigurationHandler();
		this.properties = new HashMap<String, String>();
		this.configurations = new ArrayList<String>();
		this.projects = new ArrayList<Project>();
		
		this.getStream().info("------------------------------------------------------------------------");
		this.getStream().info(" J C O M P R E S S O R");
		this.getStream().info("------------------------------------------------------------------------");
		this.getStream().empty();
	}
	
	public void loadConfiguration(final String configuration) {
		final File[] files = JcompressorUtils.getFileList(configuration);
		if (files.length > 0) {
			for (final File file : files) {
				this.addProject(this.handler.parseProjectConfiguration(file, this.properties));
			}
		}
	}
	
	public void loadProjects() {
		final List<String> configs = this.getConfigurations();
		if (configs.size() == 0) {
			this.getStream().error("No project configurations found");
		}
		
		for (final String config : configs) {
			if (StringUtils.isNotBlank(config)) {
				this.loadProject(config);
			}
		}
	}
	
	public void loadProject(final String configuration) {
		final File[] files = JcompressorUtils.getFileList(configuration);
		if (files.length > 0) {
			for (final File file : files) {
				this.addProject(this.handler.parseProjectConfiguration(file, this.properties));
			}
		}
	}
	
	public void extendProperties(final Map<String, String> properties) {
		if ((properties != null) && (properties.size() > 0)) {
			this.properties.putAll(properties);
		}
	}
	
	public void initProject(final Project project) {
		this.getStream().empty();
		this.getStream().info("------------------------------------------------------------------------");
		this.getStream().info(" " + project.getTitle());
		this.getStream().info("------------------------------------------------------------------------");
		this.getStream().empty();
		
		this.start(project, ConfigKeys.JS);
		this.start(project, ConfigKeys.CSS);
	}
	
	public void start(final Project project, final String type) {
		int count = 0;
		final LinkedList<String> files = project.getIncludes(type);
		for (final String file : files) {
			if (StringUtils.startsWith(file, ".")) {
				this.getStream().info(project.getPath() + file);
				this.getStream().info(new File(project.getPath() + file));
				files.set(count, new File(project.getPath() + file).getAbsolutePath());
			}
			
			count++;
		}
		
		this.operations(project, type);
	}
	
	public void start(final String[] values, final String type, final List<String> options) {
		final String inputFiles = values[0];
		final String outputFile = values[1];
		final List<String> files = Arrays.asList(StringUtils.split(inputFiles, ','));
		this.operations(files, type, outputFile, options);
	}
	
	protected void operations(final List<String> files, final String type, final String outputFile, final List<String> options) {
		if ((files != null) && (files.size() > 0)) {
			this.merge(files, ConfigKeys.TMP_MERGED_FILE);
			
			final File file = new File(ConfigKeys.TMP_MERGED_FILE);
			if (file.exists()) {
				this.compress(type, outputFile, options);
				this.printStatistics(outputFile, files, type);
			}
			else {
				this.getStream().info("Compression failed");
			}
		}
	}
	
	protected void operations(final Project project, final String type) {
		final LinkedList<String> files = project.getIncludes(type);
		final String outputFile = project.getOutput(type);
		this.operations(files, type, outputFile, project.getOptions());
	}
	
	protected void merge(final List<String> files, final String outputFile) {
		try {
			if (files.size() > 0) {
				final StringBuffer buffer = new StringBuffer();
				for (final String str : files) {
					final File file = new File(str);
					if (file.exists()) {
						for (final Object line : FileUtils.readLines(file)) {
							buffer.append(line);
							if (StringUtils.equals(ConfigKeys.JS, FilenameUtils.getExtension(file.getName()))) {
								buffer.append('\n');
							}
						}
					}
					else {
						this.getStream().info("File not found (" + file + ")");
					}
				}
				
				final File tmpFile = new File(outputFile);
				if (buffer.length() == 0) {
					FileUtils.deleteQuietly(tmpFile);
				}
				else {
					FileUtils.deleteQuietly(tmpFile);
					FileUtils.writeStringToFile(tmpFile, buffer.toString());
				}
			}
		}
		catch (Exception e) {
			throw new JcompressorException(e);
		}
	}
	
	protected void compress(final String type, final String output, final List<String> options) {
		final File outputPath = new File(StringUtils.substringBeforeLast(output, ConfigKeys.FILE_SEPARATOR));
		if (!outputPath.exists()) {
			outputPath.mkdirs();
		}
		
		final String[] args = this.buildYuiCompressorArgs(type, output, options);
		YUICompressor.main(args);
	}
	
	protected String[] buildYuiCompressorArgs(final String type, final String output, final List<String> options) {
		final String[] args = new String[] {
			new File(ConfigKeys.TMP_MERGED_FILE).getAbsolutePath(),
			"-o",
			output,
			"--type",
			type
		};
		
		if ((options != null) && !options.isEmpty()) {
			return (String[])ArrayUtils.addAll(args, options.toArray());
		}
		
		return args;
	}
	
	protected void printStatistics(final String outputFile, final List<String> files, final String type) {
		final File mergedFile = new File(ConfigKeys.TMP_MERGED_FILE);
		final long totalSize = JcompressorUtils.getTotalFileSize(files);
		final long mergedSize = mergedFile.length();
		final long compressedSize = new File(outputFile).length();
		
		this.getStream().info(new StringBuffer(StringUtils.upperCase(type) + ":"));
		this.getStream().info("----------------");
		this.getStream().info(new StringBuffer(StringUtils.rightPad("Total Size:", 20))
			.append(StringUtils.leftPad(JcompressorUtils.formatFileSize(totalSize), 10)));
		
		this.getStream().info(new StringBuffer(StringUtils.rightPad("Merged Size:", 20))
			.append(StringUtils.leftPad(JcompressorUtils.formatFileSize(mergedSize), 10))
			.append(StringUtils.leftPad(JcompressorUtils.percentDiff(totalSize, mergedSize), 15)));
		
		this.getStream().info(new StringBuffer(StringUtils.rightPad("Final Size:", 20))
			.append(StringUtils.leftPad(JcompressorUtils.formatFileSize(compressedSize), 10))
			.append(StringUtils.leftPad(JcompressorUtils.percentDiff(totalSize, compressedSize), 15)));
		
		this.getStream().info(new StringBuffer("Output Location: ")
			.append(outputFile));
		
		this.getStream().empty();
		FileUtils.deleteQuietly(mergedFile);
	}
	
	public void listProjects() {
		int count = 0;
		for (final Project project : this.getProjects()) {
			final StringBuffer buffer = new StringBuffer();
			buffer.append((count + 1) + ". ");
			buffer.append(project.getTitle());
			this.getStream().info(buffer.toString());
			count++;
		}
	}
	
	public DataStream getStream() {
		return this.stream;
	}
	
	public Map<String, String> getProperties() {
		return this.properties;
	}
	
	public List<String> getConfigurations() {
		return this.configurations;
	}
	
	public List<Project> getProjects() {
		return this.projects;
	}
	
	public void setStream(final DataStream stream) {
		this.stream = stream;
	}
	
	public void addProperty(final String name, final String value) {
		this.properties.put(name, value);
	}
	
	public void setConfigurations(final List<String> configurations) {
		this.configurations = configurations;
	}
	
	public void addProject(final Project project) {
		this.projects.add(project);
	}
	
}